#!/usr/bin/python3
# -*- coding: UTF-8 -*- 
# 设置utf-8  显示中文
"""
@Author: guo
@File：auto_assert.py
"""
import pytest
import re
import hamcrest
from hamcrest import *
from requests.models import Response
import jsonpath
from jsonpath import jsonpath
import sys
sys.path.append("../../")

from common.yml.get_yml_keys import GetYmlKeys

class AutoAssert:
    """进行自动断言"""
    def __init__(self):
        self.__ymlkeys = GetYmlKeys()
        self.api_data_key = self.__ymlkeys.get_api_data_key()
        self.assert_key = self.__ymlkeys.get_assert_key()

    def auto_assert(self,testcase_ymldata:dict,response:Response)->None:
        """自动断言"""
        ymldata = testcase_ymldata
        res_tmp = response

        print("\n----以下为【断言】内容:----")
        # 先判断 response的类型是否为Response,当不为Response时.将当前的case置为fail
        if type(res_tmp) != Response:
            pytest.fail(f"传入的response的类型不为Response,即<class 'requests.models.Response'>,"
                        f"故将case置为fail")

        api_data = ymldata[self.api_data_key]
        assert_data = api_data[self.assert_key]
        if assert_data == None:
            print("该接口没有写断言")

        elif isinstance(assert_data,list):

            i = 1
            for item in assert_data:
                # expression = item[self.assert_key]
                expression: str
                expression = item
                # 先判断表达式里,是否存在"res."这种字符串,如果存在,表示res为Response
                if "res." in expression:
                    res = res_tmp
                # 此时强制认为是使用 res.json(),不考虑 res.status_code
                else:
                    res = res_tmp.json()
                print(f"第【{i}】次断言，断言内容如下：")
                print(f"此次执行断言的原始表达式为:{expression}")
                """ 由于在不显示表达式里的具体内容,所以此处对表达式的内容进行了提取 """
                hamcrest_str = "assert_that"
                assume_str = "pytest.assume"
                assert_str = "assert"
                jsonpath_str = "jsonpath"
                comma_str = ","
                # exp_final = ""
                # exp_reason = ""

                # 判断是否为pytest.assume 断言
                if expression.startswith(assume_str):
                    # 去掉字符串表达式中的 pytest.assume
                    # exp = expression.lstrip(assume_str)
                    re_exp = f"^({assume_str})"
                    exp = re.sub(re_exp,"",expression)
                    # 先判断是否按要求使用了jsonpath 提取器
                    if jsonpath_str in exp:
                        # 去掉最外层的左右括号
                        exp = self.__remove_bracket(exp)
                        # 进行字段的切割
                        exp_list = exp.split(jsonpath_str)
                        # 获取jsonpath提取器的相关内容
                        exp2 = jsonpath_str + exp_list[1]
                        # 再检测左右括号的数量是否相等,不相等则进行完善
                        exp2 = self.__check_bracker(exp2)
                        # 获取jsonpath提取器的数据
                        exp2 = eval(exp2)
                        exp1 = exp_list[0]
                        # 当为str时,要加上上引号,否则在执行表达式时,会提示NameError,未定义
                        # 在pytest.assume()常见的表达式里 没有逗号","的情况,所以可以直接拼接
                        if isinstance(exp2,str):
                            exp = f"{exp1}'{exp2}'"
                        else:
                            exp = f"{exp1}{exp2}"
                        # 再检测左右括号的数量是否相等,不相等则进行完善
                        exp = self.__check_bracker(exp)
                        # 目前只考虑下面几种判断符号,至于 is True,is False 之类的暂时不考虑.
                        character_list = ['==','!=','<','<=','>','>=','in','not in']
                        exp_final = f"pytest.assume({exp})"
                        exp_reason = f"断言失败,表达式为:{exp_final}"
                        print(f"使用jsonpath提取器提取数据后的表达式为(可能会出现缺少引号的情况)：{exp_final}")
                        if '==' in exp:
                            exp_list = exp.split("==")
                            exp1 = exp_list[0]
                            exp2 = exp_list[1]
                            pytest.assume(eval(exp1)==eval(exp2),exp_reason)

                        elif '!=' in exp:
                            exp_list = exp.split("!=")
                            exp1 = exp_list[0]
                            exp2 = exp_list[1]
                            pytest.assume(eval(exp1) != eval(exp2), exp_reason)

                        elif '<' in exp:
                            exp_list = exp.split("<")
                            exp1 = exp_list[0]
                            exp2 = exp_list[1]
                            pytest.assume(eval(exp1) < eval(exp2), exp_reason)

                        elif '<=' in exp:
                            exp_list = exp.split("<=")
                            exp1 = exp_list[0]
                            exp2 = exp_list[1]
                            pytest.assume(exp1 <= exp2, exp_reason)

                        elif '>' in exp:
                            exp_list = exp.split(">")
                            exp1 = exp_list[0]
                            exp2 = exp_list[1]
                            pytest.assume(exp1 > exp2, exp_reason)

                        elif '>=' in exp:
                            exp_list = exp.split(">=")
                            exp1 = exp_list[0]
                            exp2 = exp_list[1]
                            pytest.assume(exp1 >= exp2, exp_reason)

                        elif 'not in' in exp:
                            exp_list = exp.split("not in")
                            exp1 = exp_list[0]
                            exp2 = exp_list[1]
                            pytest.assume(eval(exp1) not in eval(exp2), exp_reason)

                        elif 'in' in exp:
                            exp_list = exp.split("in")
                            exp1 = exp_list[0]
                            exp2 = exp_list[1]
                            pytest.assume(eval(exp1) in eval(exp2), exp_reason)


                        # 非常见的判断方式,直接使用eval
                        else:

                            exp = f"""pytest.assume({exp},"断言失败，表达式为："{exp_final})"""
                            # pytest.assume(eval(exp),exp_reason)
                            eval(exp_final)

                    # 写的断言表达式里,没有使用jsonpath提取器.此时直接进行断言
                    else:
                        # 由于去掉了pytest.assume,所以先检测括号的完整性.
                        exp = self.__check_bracker(exp)
                        exp_final = f"pytest.assume({exp})"
                        exp_reason = f"断言失败,表达式为: {exp_final}"
                        pytest.assume(eval(exp),exp_reason)

                # 判断是否为hamcrest 断言
                elif expression.startswith(hamcrest_str):
                    # exp = expression.lstrip(hamcrest_str)
                    re_exp=f"^({hamcrest_str})"
                    exp = re.sub(re_exp,"",expression)
                    # 判断是否按要求使用了jsonpath 提取器
                    if jsonpath_str in exp:
                        # 去掉最外层的左右括号
                        exp = self.__remove_bracket(exp)
                        # 进行字段的切割
                        exp_list = exp.split(jsonpath_str)
                        # 获取jsonpath提取器的相关内容
                        exp2 = jsonpath_str + exp_list[1]
                        # 再检测左右括号的数量是否相等,不相等则进行完善
                        exp2 = self.__check_bracker(exp2)
                        exp2 = eval(exp2)
                        exp1 = exp_list[0]

                        # 先判断exp2 是否为str,若为str时,则要进行加上引号
                        if isinstance(exp2,str):
                            exp2 = f"'{exp2}'"
                        # 判断逗号是否存在于exp1中
                        if comma_str in exp1:
                            exp1_list = exp1.split(comma_str)
                            # 此时只要表达式写的正确,exp1_list的个数为2 ,暂时不考虑不为2的情况
                            exp1 = exp1_list[0]
                            exp2 = f"{exp1_list[1]}{exp2}"
                            # 再进行一次括号检查补全
                            exp2 = self.__check_bracker(exp2)
                            exp_final = f"assert_that({exp1},{exp2})"
                            exp_reason = f"断言失败,断言表达式为: {exp_final}"
                            print(f"使用jsonpath提取器提取数据后的表达式为(可能会出现缺少引号的情况)：{exp_final}")
                            # 要转化为表达式
                            assert_that(eval(exp1),eval(exp2),reason=exp_reason)

                        # # 表示exp1里没有逗号,此时直接进行拼串
                        # else:
                        #     exp = f"{exp1}{exp2}"
                        #     # 再次检测括号是否完善
                        #     exp = self.__check_bracker(exp)
                        #     exp_final = f"assert_that({exp})"
                        #     exp_reason = f"断言失败,表达式为: {exp_final}"
                        #     print(f"使用jsonpath提取器提取数据后的表达式为(可能会出现缺少引号的情况)：{exp_final}")
                        #     assert_that(exp,reason=exp_reason)

                        # 表示exp1里没有逗号,表示表达式书写错误,暂时不考虑
                        else:
                            pytest.fail(f"表达式书写错误,断言方式,使用是hamcrest,该表达式里,应该有英文的逗号','但是未检测到.\n"
                                        f"yml文件中的表达式为:{item}\n"
                                        f"故将该用例置为fail")

                    # 表示没有按要求使用jsonpath 提取器
                    else:
                        # 判断是否有逗号存在
                        if comma_str in exp:
                            # 先去掉最外层的括号
                            exp = self.__remove_bracket(exp)
                            exp_list = exp.split(comma_str)
                            # 暂时不考虑切割后的个数不等于2的情况
                            exp1 = exp_list[0]
                            exp2 = exp_list[1]
                            # 检测括号的完整性
                            exp2 = self.__check_bracker(exp2)
                            exp_final = f"assert_that({exp1},{exp2})"
                            exp_reason = f"断言失败,表达式为: {exp_final}"
                            print(f"表达式为: {exp_final}")
                            assert_that(eval(exp1),eval(exp2),reason=exp_reason)

                        # 表示没有逗号,直接将该case置为fail
                        else:
                            pytest.fail(f"表达式书写错误,断言方式,使用是hamcrest,该表达式里,应该有英文的逗号','但是未检测到.\n"
                                        f"yml文件中的表达式为:{item}\n"
                                        f"故将该用例置为fail")


                # 执行表达式, 经过多次的验证,不建议对表达式再次进行拼接,因为会存在出现值 没有引号等一系列的情况
                # 所以最简单的方法,就是直接执行原始表达式.

                # 表示使用的是其他的断言方式，本框架不支持
                else:
                    print(f"本框架的断言，只支持pytest.assume() 和 hamcrest() 两种断言方式，且两种可混用。")
                    eval(item)
                # 计数器加1
                i+=1
                print("\n")


        else:
            pytest.fail(f"由于assert 断言的格式书写错误,所以将该用例的运行结果置为:fail , 请参照样例重新填写.如下面的例子:\n"
                        f"api_data: \n"
                        f"  .....\n"
                        f"  assert:\n"
                        f"    - pytest.assume(1<2)\n"
                        f"    - assert_that(1,close_to(2,0.25))\n"
                        f"或assert: [pytest.assume(1<2),assert_that(1,close_to(2,0.25))]")



    def __remove_bracket(self,exp:str)->str:
        """去掉表达式里的最外层的括号"""
        left_bracket = "("
        right_bracket = ")"
        exp = exp
        # 去年最外层的左右括号
        exp = exp.lstrip(left_bracket).rstrip(right_bracket)
        return exp

    def __check_bracker(self, exp:str)->str:
        """检测表达式里的左右括号是否相等,若不相等,则进行补充完善,并返回"""
        left_bracket = "("
        right_bracket = ")"
        # 获取各自的左右括号的数数量
        left_count = exp.count(left_bracket)
        right_count = exp.count(right_bracket)
        value = left_count - right_count
        if value == 0:
            pass

        elif value<0:
            exp = left_bracket * abs(value) + exp

        else:
            exp = exp + right_bracket * value

        return exp




